package mini;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.StringTokenizer;
import java.util.logging.Logger;

import mini.compiler.CodeGenerator;
import mini.compiler.Linker;
import mini.compiler.Optimizer;
import mini.compiler.ProgramRewriter;
import mini.expr.FunctionCall;
import mini.util.SymbolicNames;

public class Program {
	
	// It's big, it's heavy, it's wood!
	private Logger log = Logger.getLogger( "mini.Program" );

    private StatementList s;

    public static final String NEWLINE = System.getProperty("line.separator");
    
    // The first call to writeSymblicFileName writes to this file
    private static final String DEFAULT_SYMBOLIC_FILENAME = "resources/output.unopt.sym";

    // This is where the optimize() method writes to
    private static final String DEFAULT_OPT_PROG_FILENAME = "resources/output.opt.sym";

    // This is where the rewrite() method writes to
    private static final String DEFAULT_REWRITTEN_PROG_FILENAME = "resources/output.opt.rewritten.sym";
    private static final String DEFAULT_UNOPT_REWRITTEN_PROG_FILENAME = "resources/output.unopt.rewritten.sym";

    // This is where the link() method writes the optimized version to
    private static final String DEFAULT_LINKED_PROG_FILENAME = "resources/output.opt.lnk";
    private static final String DEFAULT_LINKED_MEM_FILENAME = "resources/output.opt.mem";

    // This is where the link() method writes the unoptimized version to
    private static final String DEFAULT_UNOPT_LINKED_PROG_FILENAME = "resources/output.unopt.lnk";
    private static final String DEFAULT_UNOPT_LINKED_MEM_FILENAME = "resources/output.unopt.mem";

    private static final String END_OF_SOURCE_CODE = "Dumping out all the variables...";

    public Program(StatementList list){
    	s = list;
    }

    public void eval(HashMap<String,Integer> hm, HashMap<String, Proc> functiontable, LinkedList var){
    	s.eval(hm, functiontable, var);
    }

    public void parse(HashMap<String,Integer> hm, HashMap<String, Proc> functiontable, LinkedList var) {
    	
    	// Remember to add to the function table symbols for the SP, FP, and FPB
    	hm.put( SymbolicNames.getSymbolicNameForFP(), SymbolicNames.getMemoryLocForFP());
    	hm.put( SymbolicNames.getSymbolicNameForSP(), SymbolicNames.getMemoryLocForSP());
    	hm.put( SymbolicNames.getSymbolicNameForFPB(), SymbolicNames.getMemoryLocForFPB());
    	hm.put( SymbolicNames.getSymbolicNameForScratch(), SymbolicNames.getMemoryLocForScratch());
    	
    	// First we do some parsing to generate the symbolic code
    	String output = s.parse(hm, functiontable, var);
    	log.info( "We discovered " + functiontable.size() + " functions in the file." );
    	
    	// Make sure to add a HLT instruction at the end!
    	CodeGenerator cg = new CodeGenerator();
    	cg.appendCode( output );
    	cg.addHalt();
    	output = cg.getCode();
    	
    	// And write the symbolic code out to a file
    	writeSymbolicCode(output,hm,functiontable,var);
    	
    	// And the second optimizing step
    	optimize();
    	
    	// Third step is replacing the 'LDA' and 'STA' calls to a
    	// series of calls that will look for the values in the
    	// activation record. See slide 32, basically each LDA and
    	// STA will get replaced with four lines, using the FP
    	// and FPB and some arithmetic
    	rewriteLoadAndSaveCallsToUseAR(hm,functiontable);

    	// This does the final step of linking
    	link(hm,functiontable);
    }

    /**
     * At this point, the optimized code files refer to a lot of
     * variables that don't exist in global memory, because they
     * are local user and temp variables to some procedure. So
     * lines like:
     * 
     * LDA temp1
     * 
     * Have to be rewritten, according to slide 33, into four lines
     * that look like:
     * 
     * LDA FP
     * ADD <symbol>
     * STA FPB
     * LDI FBC
     * 
     * What's in the symbol is the offset to the variable in the
     * activation record. If you know what procedure you're in,
     * you can use the findSymbolicOffsetForVariable() method
     * on the current Proc object to see what <symbol> should be.
     * 
     * One trick is that you have to figure out what procedure
     * you're in. Procedures start with a line that looks like:
     * 
     * ProcedureN: LDA temp1
     * 
     * You can use the string "ProcedureN" and compare it to the
     * names of the procedure objects (found with the getLocationLabel()
     * method on procedures) until you find one that matches.
     * 
     * @param hm Symbol table
     * @param functiontable Function table
     */
    private void rewriteLoadAndSaveCallsToUseAR( HashMap<String,Integer> hm, HashMap<String,Proc> functiontable ) {
		// Now call the rewriter to get the rewritten code written out
    	BufferedReader inputReader = null;
    	BufferedWriter outputWriter = null;

		try {
			// First for the optimized version
			inputReader = new BufferedReader( new FileReader( new File(DEFAULT_OPT_PROG_FILENAME)) );
	    	outputWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_REWRITTEN_PROG_FILENAME)) );
	    	(new ProgramRewriter()).rewrite(hm,functiontable, inputReader, outputWriter);

	    	// Close them out
	    	inputReader.close();
	    	outputWriter.close();
	    	
	    	// Then for the unoptimized version
			inputReader = new BufferedReader( new FileReader( new File(DEFAULT_SYMBOLIC_FILENAME)) );
	    	outputWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_UNOPT_REWRITTEN_PROG_FILENAME)) );
	    	(new ProgramRewriter()).rewrite(hm,functiontable, inputReader, outputWriter);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if( inputReader != null ) { 
				try {
					inputReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if( outputWriter != null ) { 
				try {
					outputWriter.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
     * Linking is done by reading in the symbolic file that was written previously,
     * opening some output files, and passing it off to the linker
     * @param hm Maps constants to variable names, which we need for 
     *           setting up memory. These start with 'Constant' as their
     *           name.
     */
	private void link( HashMap<String,Integer> hm, HashMap<String,Proc> functiontable ) {
		// Now call the linker to get the linked code written out
    	BufferedReader inputReader = null;
    	BufferedWriter progWriter = null;
    	BufferedWriter memWriter = null;
		try {
			inputReader = new BufferedReader( new FileReader( new File(DEFAULT_REWRITTEN_PROG_FILENAME)) );
	    	progWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_LINKED_PROG_FILENAME)) );
	    	memWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_LINKED_MEM_FILENAME)) );
	    	
	    	// Before you link, you need to add any constants that are floating around in the
	    	// procedures to the symbol table, so they get written out properly as constants
	    	// in the global memory area
	    	for( Proc proc : functiontable.values() ) {
	    		for( String constant : proc.getConstants().keySet() ) {
	    			hm.put( constant, proc.getConstants().get(constant) );
	    		}
	    	}
	    	
	    	// And now we can link
	    	(new Linker()).link(hm, functiontable, inputReader, memWriter, progWriter);
	    	
	    	inputReader.close();
	    	progWriter.close();
	    	memWriter.close();
	    	
	    	// Now write the unoptimized version
			inputReader = new BufferedReader( new FileReader( new File(DEFAULT_UNOPT_REWRITTEN_PROG_FILENAME)) );
	    	progWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_UNOPT_LINKED_PROG_FILENAME)) );
	    	memWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_UNOPT_LINKED_MEM_FILENAME)) );
	    	(new Linker()).link(hm, functiontable, inputReader, memWriter, progWriter);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if( inputReader != null ) { 
				try {
					inputReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if( progWriter != null ) { 
				try {
					progWriter.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if( memWriter != null ) { 
				try {
					memWriter.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
 
    // Optimizing is done by reading in the unoptimized memory and program files,
    // opening some output files, and passing it off to the optimizer
	private void optimize() {
		// Now call the optimizer to get the optimized code written out
    	BufferedReader progReader = null;
    	BufferedWriter progWriter = null;
		try {
			progReader = new BufferedReader( new FileReader( new File(DEFAULT_SYMBOLIC_FILENAME)) );
	    	progWriter = new BufferedWriter( new FileWriter( new File(DEFAULT_OPT_PROG_FILENAME)) );
	    	(new Optimizer()).optimize(progReader, progWriter);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if( progReader != null ) { 
				try {
					progReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if( progWriter != null ) { 
				try {
					progWriter.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
 
    /**
     * This is the first stage of compiling. It's generated symbolic code,
     * but not linked or optimized. It's laid out in memory so that the
     * main program is first, followed by the functions in order
     * 
     * @param output The main program's code
     * @param functiontable The functions
     * @param var I don't think we use, but supposedly a list of variables
     */
    private void writeSymbolicCode(String output, HashMap<String,Integer> hm, HashMap<String, Proc> functiontable,
    		LinkedList<String> var) {
    	
    	// The first thing we want to do is turn the main code into a function, and
    	// call it -- this way we can treat everything the same, like everything is
    	// a function
    	StringBuffer mainProcCode = new StringBuffer();
		StringTokenizer strtok = new StringTokenizer( output, NEWLINE );
		boolean keepGoing = true; 
		while( strtok.hasMoreTokens() && keepGoing ) {
			String line = strtok.nextToken().trim();
			if( line.equals(Program.END_OF_SOURCE_CODE) ) {
				keepGoing = false;
			} else if( line.trim().length() > 0 ) {
				mainProcCode.append( line.trim() );
				// If the last line we read was a label, we don't add a newline; this is so
				// two consecutive lines of:
				// loc3:
				// HLT
				// Actually get written as:
				// loc3: HLT
				if( isLocationLabel(line) ) {
					mainProcCode.append( " " );	// We do need to space after the label
				} else {
					mainProcCode.append( NEWLINE );
				}
			}
		}
		Proc mainProc = new Proc( hm, mainProcCode.toString().trim() );
		functiontable.put( mainProc.getLocationLabel(), mainProc );
		
		// We probably need to make a function call to it as the first line of the code
		FunctionCall maincall = new FunctionCall( mainProc.getLocationLabel(), null );
		// Need to note that we're not expecting (and haven't allocated space for) a return vaue
		maincall.setHasReturnValue( false );
    	
		// Then we need to write out first the main procedure (first, because it's assumed to be first), 
		// then all the other functions, to the file
    	try {
    		// This is where output goes
			BufferedWriter writer = new BufferedWriter( new FileWriter( new File(DEFAULT_SYMBOLIC_FILENAME) ) );
			
			// Print out the function call to main that is going to start this
			writer.write( maincall.parse(hm, functiontable, var));

			// Print out all the functions, starting with the main first
			boolean needsNewline = false;
			writer.write( mainProc.getTheCode() );
			for( Proc proc : functiontable.values() ) {
				// We already wrote the main proc out first
				if( proc != mainProc ) {
					if( needsNewline ) {
						writer.write( Program.NEWLINE );
					}
					writer.write( proc.getTheCode() );
					needsNewline = true;
				}
			}
			
			// Close it out
			writer.close();
		} catch (IOException e) {
			System.err.println( "Could not create output symbolic file to: " + DEFAULT_SYMBOLIC_FILENAME );
			e.printStackTrace();
			System.exit(1);
		}
	}

	/**
     * What identifies a location label is that it ends with :
     * @param line The trimmed line
     * @return True if it's a location label, false otherwise
     */
	private boolean isLocationLabel(String line) {
		return line.endsWith(":");
	}

	public void dump(HashMap<String,Integer> hm, HashMap<String, Proc> functiontable, LinkedList<String> var){
		int n = var.size();
		int i;
		String s;
		System.out.println("Dumping out all the variables...");
		for (i=0; i<n; i++){
		    s = (String)var.get(i);
		    System.out.println(s + "=" + hm.get(s).toString());
		}
    }
}
